package command
{
   import flash.display.BitmapData;
   import flash.display.Sprite;
   import flash.events.Event;
   import flash.geom.Matrix;
   import flash.geom.Point;
   import flash.geom.Rectangle;
   import flash.utils.Dictionary;
   // progression
   import jp.progression.commands.Command;
   // greensock
   import com.greensock.TweenMax;
   import com.greensock.TimelineMax;
   import com.greensock.easing.Cubic;
   import com.greensock.easing.Quad;
   import com.greensock.events.TweenEvent;

    /**
    * @author  cjboy | cjboy1984@gmail.com
    * @version Apr,15,2011
    * @example
    * !!! Almost familiar to Greensock !!!
    *    - TODO : 測試 hide:false，回復特效是否正常
    *
    * // vanishingPoint is based on the coordinates of the mc.
    * addCommand(
    *    new DoGinnyEffect(mc, 1, { vanishingPoint:new Point(100, 100), hide:true, autoDestroy:false } )
    * );
    */
   public class DoGinnyEffect extends Command
   {
      private const BMP_NAME:String = '_gs_ginny_';
      private const SEGMENT:int = 20;

      private var timeline:TimelineMax;
      private var ta:Sprite;
      private var parameters:Object;
      private var duration:Number;

      // width & height of the target.
      private var taWidth:Number;
      private var taHeight:Number;
      // The effect bitmap.
      private var bmp:Sprite;
      private var bmpData:BitmapData;
      // vertex
      private var vertexs:Array;
      // user given properties.
      private var vanishingPoint:Point;
      private var hide:Boolean;
      private var autoDestroy:Boolean;

      // onUpdate, onCompleteFunc
      private var onStartFunc:Function;
      private var onUpdateFunc:Function;
      private var onCompleteFunc:Function;

      // dictionaries
      private static var _nameDict:Dictionary = new Dictionary();

      public function DoGinnyEffect(target:Sprite, dur:Number, param:Object, initObj:Object = null)
      {
         // 利用 myExecute 執行，myInterrupt 中斷
         super(myExecute, myInterrupt, initObj);

         // basic properties.
         ta = target;
         duration = dur <= 0 ? 0.01 : dur;
         parameters = param;

         // init properties.
         taWidth = ta.width;
         taHeight = ta.height;
         vanishingPoint = (param.vanishingPoint == undefined) ? new Point(0, 0) : param.vanishingPoint;
         hide = (param.hide == undefined) ? false : param.hide;
         autoDestroy = (param.autoDestroy == undefined) ? true : param.autoDestroy;
         onStartFunc = (param.onStart == undefined) ? null : param.onStart;
         onUpdateFunc = (param.onUpdate == undefined) ? null : param.onUpdate;
         onCompleteFunc = (param.onComplete == undefined) ? null : param.onComplete;
      }

      // ################### protected ##################

      override public function executeComplete():void
      {
         super.executeComplete();

         // 清掉tween
         timeline = null;

         // 移掉rendering
         ta.removeEventListener(Event.ENTER_FRAME, render);

         // onCompleteFunc
         if (onCompleteFunc != null)
         {
            onCompleteFunc.apply();
         }

         if (autoDestroy)
         {
            ta.visible = true;

            // name dictionary
            delete _nameDict[ta];

            // destory bmpData
            bmpData.dispose();
            bmpData = null;

            // remove effect bmp.
            if (ta.parent && ta.parent.contains(bmp))
            {
               ta.parent.removeChild(bmp);
            }

            // destroy effect bmp.
            bmp.graphics.clear();
            bmp = null;
         }
      }

      override public function clone():Command
      {
         return new DoGinnyEffect(ta, duration, parameters);
      }

      // #################### private ###################

      private function myExecute():void
      {
         // find name from dictionary.
         var nname:String;
         if (_nameDict[ta] == undefined)
         {
            nname = BMP_NAME + ta.name;
            _nameDict[ta] = nname;
         }
         else
         {
            nname = _nameDict[ta] as String;
         }

         // find bmp from name.
         bmp = ta.parent.getChildByName(nname) as Sprite;
         if (bmp == null)
         {
            bmp = new Sprite();
            bmp.name = nname;
         }
         var taIndex:int = ta.parent.getChildIndex(ta);
         ta.parent.addChildAt(bmp, taIndex);
         ta.visible = false;

         // render 1st time
         firstRender();
         // start rendering
         ta.addEventListener(Event.ENTER_FRAME, render);

         // do effect
         timeline = new TimelineMax();
         timeline.addEventListener(TweenEvent.START, myStart, false, int.MAX_VALUE, true);
         timeline.addEventListener(TweenEvent.UPDATE, myUpdate, false, int.MAX_VALUE, true);
         timeline.addEventListener(TweenEvent.COMPLETE, myComplete, false, int.MAX_VALUE, true);
         var xx:int, yy:int, delay:Number;
         // convert coordinates from "ta" to "bmp".
         var gPoint:Point = ta.localToGlobal(vanishingPoint);
         var lPoint:Point = bmp.globalToLocal(gPoint);
         var px:Number = SEGMENT * (lPoint.x / taWidth);
         var py:Number = SEGMENT * (lPoint.y / taHeight);

         // info for delay and duration.
         var offx:Number = (SEGMENT - 1 - px);
         var offy:Number = (SEGMENT - 1 - py);
         var max_delay:Number = Math.sqrt(offx * offx + offy * offy);

         if (hide)
         {
            for (xx = 0; xx < SEGMENT; ++xx)
            {
               for (yy = 0; yy < SEGMENT; ++yy)
               {
                  vertexs[xx][yy] = new Point(xx * taWidth / SEGMENT, yy * taHeight / SEGMENT);
                  delay = Math.sqrt((xx - px) * (xx - px) + (yy - py) * (yy - py)) / max_delay * duration;
                  //trace('xx =', xx, '; yy =', yy, '; max_delay =', max_delay, '; delay =', delay);

                  timeline.insert(TweenMax.to(vertexs[xx][yy], delay, { x:px * taWidth / SEGMENT, y:py * taHeight / SEGMENT, delay:delay / 2, ease:Cubic.easeIn } ) );
               }
            }
         }
         else
         {
            var max:Number = 0;
            for (xx = 0; xx < SEGMENT; ++xx)
            {
               for (yy = 0; yy < SEGMENT; ++yy)
               {
                  vertexs[xx][yy] = new Point(px * taWidth / SEGMENT, py * taHeight / SEGMENT);
                  delay = Math.sqrt((xx - px) * (xx - px) + (yy - py) * (yy - py));
                  max = Math.max(max, delay);
               }
            }
            for (xx = 0; xx < SEGMENT; ++xx)
            {
               for (yy = 0; yy < SEGMENT; ++yy)
               {
                  delay = Math.sqrt((xx - px) * (xx - px) + (yy - py) * (yy - py)) / max_delay * duration;

                  timeline.insert(TweenMax.to(vertexs[xx][yy], delay + 0.05, { x:xx * taWidth / SEGMENT, y:yy * taHeight / SEGMENT, delay:delay / 2, ease:Quad.easeOut } ) );
               }
            }
         }

         if (timeline)
         {
            return;
         }
         else
         {
            executeComplete();
         }
      }

      private function myStart(e:TweenEvent):void
      {
         // onCompleteFunc
         if (onStartFunc != null)
         {
            onStartFunc.apply();
         }
      }

      private function myUpdate(e:TweenEvent):void
      {
         // onCompleteFunc
         if (onUpdateFunc != null)
         {
            onUpdateFunc.apply();
         }
      }

      private function myComplete(e:TweenEvent):void
      {
         timeline.removeEventListener(TweenEvent.START, myStart, false);
         timeline.removeEventListener(TweenEvent.UPDATE, myUpdate, false);
         timeline.removeEventListener(TweenEvent.COMPLETE, myComplete, false);

         executeComplete();
      }

      // TODO: figure out progression interrupt();
      private function myInterrupt():void
      {
         timeline.kill();
         timeline = null;

         ta.removeEventListener(Event.ENTER_FRAME, render);

         interrupt();
      }

      // --------------------- LINE ---------------------

      private function firstRender():void
      {
         // get boundary rectangle relative to the coordinate system of ta.parent
         var taParentBound:Rectangle = ta.getBounds(ta.parent);

         // get boundary rectangle relative to the coordinate system of ta
         var taBound:Rectangle = ta.getBounds(ta);

         // cache target as BitmapData.
         var mx:Matrix = new Matrix();
         mx.translate( -taBound.x, -taBound.y);
         mx.scale(ta.scaleX, ta.scaleY);
         bmpData = new BitmapData(taWidth, taHeight, true, 0x00000000);
         bmpData.draw(ta, mx, null, null, null, true);

         // calculate triangles.
         var vertices:Vector.<Number> = new Vector.<Number>();
         var indices:Vector.<int> = new Vector.<int>();
         var uvtData:Vector.<Number> = new Vector.<Number>();

         vertexs = [];
         for (var xx:int = 0; xx < SEGMENT; ++xx)
         {
            vertexs[xx] = [];
            for (var yy:int = 0; yy < SEGMENT; ++yy)
            {
               vertexs[xx][yy] = new Point(xx * taWidth / SEGMENT, yy * taHeight / SEGMENT);

               vertices[vertices.length] = vertexs[xx][yy].x
               vertices[vertices.length] = vertexs[xx][yy].y

               uvtData[uvtData.length] = xx / SEGMENT;
               uvtData[uvtData.length] = yy / SEGMENT;
            }
         }

         for (var i:int = 0; i < SEGMENT - 1; ++i)
         {
            for (var j:int = 0; j < SEGMENT - 1; ++j)
            {
               indices.push(i * SEGMENT + j, i * SEGMENT + j + 1, (i + 1) * SEGMENT + j);
               indices.push(i * SEGMENT + j + 1, (i + 1) * SEGMENT + 1 + j, (i + 1) * SEGMENT + j);
            }
         }

         // draw target using triangles.
         bmp.x = taParentBound.x;
         bmp.y = taParentBound.y;
         bmp.graphics.clear();
         bmp.graphics.beginBitmapFill(bmpData);
         bmp.graphics.drawTriangles(vertices, indices, uvtData);
         bmp.graphics.endFill();
      }

      private function render(e:Event = null):void
      {
         // calculate triangles.
         var vertices:Vector.<Number> = new Vector.<Number>();
         var indices:Vector.<int> = new Vector.<int>();
         var uvtData:Vector.<Number> = new Vector.<Number>();

         for (var xx:int = 0; xx < SEGMENT; ++xx)
         {
            for (var yy:int = 0; yy < SEGMENT; ++yy)
            {
               vertices[vertices.length] = vertexs[xx][yy].x
               vertices[vertices.length] = vertexs[xx][yy].y
               uvtData[uvtData.length] = xx / SEGMENT;
               uvtData[uvtData.length] = yy / SEGMENT;
            }
         }

         for (var i:int = 0; i < SEGMENT - 1; ++i)
         {
            for (var j:int = 0; j < SEGMENT - 1; ++j)
            {
               indices.push(i * SEGMENT + j, i * SEGMENT + j + 1, (i + 1) * SEGMENT + j);
               indices.push(i * SEGMENT + j + 1, (i + 1) * SEGMENT + 1 + j, (i + 1) * SEGMENT + j);
            }
         }

         // draw target using triangles.
         bmp.graphics.clear();
         bmp.graphics.beginBitmapFill(bmpData);
         bmp.graphics.drawTriangles(vertices, indices, uvtData);
         bmp.graphics.endFill();
      }

      // --------------------- LINE ---------------------

   }

}